﻿namespace IBTrader.Modules.Market
{
    using IBTrader.Model;
    using IBTrader.Properties;
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;

    internal class Expirations 
    {
        private enum IndexFuture { SPX, NDX, INDU };
        internal static string Expiration(string symbol, DateTime dt)
        {
            var future = Settings.Default.Futures.SingleOrDefault(f => f.Symbol.Equals(symbol, StringComparison.OrdinalIgnoreCase));
            if (future != null)
                return Expiration(future, dt);

            IndexFuture index;
            if (Enum.TryParse<IndexFuture>(symbol, out index))
                return Expiration(index, dt);

            return null;
        }
        /// <summary>
        /// SPX,NDX: Last Month in Quater, 2. Friday in Month - 1 = Thursday
        /// INDU   : Last Month in Quater, 3. Friday in Month - 1 = Thursday
        /// </summary>
        /// <param name="future">Index future.</param>
        /// <returns>Expiration.</returns>
        private static string Expiration(IndexFuture future, DateTime date)
        {
            int fridaysOff = future == IndexFuture.INDU ? 2 : 1;

            var month = (int)(date.Month / 3 * 3);
            var days = Enumerable.Range(1, DateTime.DaysInMonth(date.Year, month)).Select(i => new DateTime(date.Year, month, i));
            var expiration = days.Where(day => day.DayOfWeek == DayOfWeek.Friday).Skip(fridaysOff).First().AddDays(-1).ToString("yyyyMM");
            return expiration;
        }
        private static readonly Dictionary<string, Dictionary<DateTime, string>> expirations =
            new Dictionary<string, Dictionary<DateTime, string>>();
        private static string Expiration(Future future,  DateTime date)
        {
            // + 1 to avoid IB Margin Violation Warning on last day
            // SI,HG: IB Margin Violation Warning on First Notice - 2
            // + 3 for trade start include summertime offset
            date = date.Date;
            if (!expirations.ContainsKey(future.Expirations))
                expirations.Add(future.Expirations, new Dictionary<DateTime, string>());
            if (!expirations[future.Expirations].ContainsKey(date))
                expirations[future.Expirations].Add(date, (Settings.Default[future.Expirations] as StringCollection).OfType<string>().Select(s => DateTime.Parse(s))
                .Where(d => d >= AddWorkingDays(date, future.Days)).Min().AddMonths(future.Month).ToString("yyyyMM"));
            //return (Settings.Default[future.Expirations] as StringCollection).OfType<string>().Select(s => DateTime.Parse(s))
            //    .Where(d => d >= AddWorkingDays(date.Date, future.Days)).Min().AddMonths(future.Month).ToString("yyyyMM");
            return expirations[future.Expirations][date];
        }
        private static DateTime AddWorkingDays(DateTime dt, int cnt)
        {
            while (cnt > 0)
            {
                dt = dt.AddDays(1);
                if (!new[] { DayOfWeek.Saturday, DayOfWeek.Sunday }.Contains(dt.DayOfWeek) && !Holidays.Contains(dt.Date))
                    cnt--;
            }
            return dt;
        }
        private static IEnumerable<DateTime> Holidays
        {
            get
            {
                return Settings.Default.Holidays.OfType<string>().Select(s => DateTime.Parse(s));
            }
        }
    }
}